# -*- coding: gbk -*-

import os, stat

class FileError (Exception): pass


class PageFile:
    def __init__ (self, baseName, pageSize, chunkSize):
        self.baseName = baseName
        self.pageSize = pageSize
        self.chunkSize = chunkSize
        self.chunkNumPages = chunkSize / pageSize

        self.cacheFp = None
        self.cacheChunk = -1

        self.lastChunk, self.lastNumPages = self.detectLastChunk()
        if (self.lastChunk == -1):
            self.newChunk() # Create the first chunk

    def close (self):
        if (self.cacheChunk != -1):
            self.cacheFp.close()

    def __del__ (self):
        self.close()
        

    def fileName (self, chunk):
        return '%s.%d' % (self.baseName, chunk)    

    def detectLastChunk (self):
        chunk = -1
        while (os.access(self.fileName(chunk + 1), os.F_OK)):
            chunk += 1
        if (chunk == -1):
            return (-1, 0)
        else:
            realSize = os.stat(self.fileName(chunk))[stat.ST_SIZE]
            return (chunk, realSize / self.pageSize)

    def newChunk (self):
        chunkNum = self.lastChunk + 1
        fp = None

        # Touch the file
        try:
            fp = open(self.fileName(chunkNum), 'w')
        finally:
            if (fp): fp.close()
        self.lastChunk = chunkNum
        self.lastNumPages = 0
        return self.setChunk(chunkNum)

    def setChunk (self, chunkNum):
        if (chunkNum < 0):
            raise FileError, 'chunkNum should >= 0'
        
        if (self.cacheChunk == chunkNum):
            return self.cacheFp

        if (chunkNum > self.lastChunk):
            raise FileError, 'chunkNum > lastChunk'
        try:
            fp = open(self.fileName(chunkNum), 'r+')
            if (self.cacheFp): self.cacheFp.close()
            self.cacheFp = fp
            self.cacheChunk = chunkNum
            return fp
        except: raise        
        
        
    def append (self, header):
        if (len(header) > self.pageSize):
            raise FileException, 'Header too large'
        
        pageData = header + ' ' * (self.pageSize - len(header))
        
        if (self.lastNumPages >= self.chunkNumPages):
            # Turn to next chunk
            fp = self.newChunk ()
        else:
            fp = self.setChunk (self.lastChunk)

        pageNum = self.lastNumPages            
        # Seek to there and write page
        fp.seek(self.lastNumPages * self.pageSize)
        fp.write(pageData)
        
        self.lastNumPages += 1
        return pageNum + self.lastChunk * self.chunkNumPages

    def read (self, pageNum, off = 0, size = 0):
        if (size == 0): size = self.pageSize        
        chunkNum = pageNum / self.chunkNumPages
        chunkPageNum = pageNum % self.chunkNumPages

        fp = self.setChunk(chunkNum)
        fp.seek(chunkPageNum * self.pageSize + off)
        return fp.read(size)

    def modify (self, pageNum, content, off = 0):
        chunkNum = pageNum / self.chunkNumPages
        chunkPageNum = pageNum % self.chunkNumPages

        fp = self.setChunk(chunkNum)
        fp.seek(chunkPageNum * self.pageSize + off, 0)
        fp.write(content)
        return True    
        
if __name__ == '__main__':
    f = PageFile('test', 4, 8)
    print f.append('A')
    print f.append('B')
    print f.append('C')
    print f.append('D')
    f.modify(0, 'B', 1)
    f.modify(2, 'D', 2)
    print f.read(0)
    print f.read(3)
    
        
        
        
        

        

        
        

    

    
